#include <hash/topk.h>
#include <hash/dataset.h>
#include <hash/LSH.h>
#include <hash/composite.h>
#include <hash/LSH_index.h>
#include <hash/hash_table.h>
#include <hash/bruteforce.h>
#include <hash/multiprobeLSH.h>
#include <hash/embedding.h>
#include <hash/eval.h>
#include <hash/common.h>

#include <hash/timer.h>

using namespace cspace_learning;

void generate_data(Dataset& dataset, unsigned int N, unsigned int dim);

void bruteforce_line_knn(const Dataset& dataset, float* a, float* v);

void bruteforce_line_knn2(const Dataset& dataset, float* a, float* v);

void line_knn(const Dataset& dataset, float* a, float* v);
void line_mpknn(const Dataset& dataset, float* a, float* v);
void line_spase_rpknn(const Dataset& dataset, float* a, float* v);
void line_rpknn(const Dataset& dataset, float* a, float* v);

void bruteforce_knn(const Dataset& dataset, float* query);

void general_test();
void test_dataset(const std::string& dataset_filename, const std::string& query_filename);



int main()
{
  srand(time(NULL));

  // general_test();

  std::string dataset_filename1("test/collision_database.txt");
  std::string dataset_filename2("test/collision_free_database.txt");
  std::string query_filename("test/linequery.txt");

  test_dataset(dataset_filename2, query_filename);

  return 1;
}


void general_test()
{
  float ub = 1, lb = 0;

  unsigned int dim = 10;
  unsigned int Q = 1;
  unsigned int N = 100000;

  Dataset dataset(dim);
  generate_data(dataset, N, dim);

  float* v = new float[dim];
  float* a = new float[dim];

  for(unsigned int i = 0; i < Q; ++i)
  {
    for(unsigned int d = 0; d < dim; ++d)
      a[d] = (ub - lb) * (rand() / (float)RAND_MAX) + lb;

    for(unsigned int d = 0; d < dim; ++d)
      v[d] =  (ub - lb) * (rand() / (float)RAND_MAX) + lb;
    float len = 0;
    for(unsigned int d = 0; d < dim; ++d)
      len += v[d] * v[d];
    len = sqrt(len);
    for(unsigned int d = 0; d < dim; ++d)
      v[d] /= len;

    bruteforce_line_knn(dataset, a, v);
    bruteforce_line_knn2(dataset, a, v);

    line_knn(dataset, a, v);
    line_mpknn(dataset, a, v);
    line_rpknn(dataset, a, v);
    line_spase_rpknn(dataset, a, v);
  }

  delete [] a;
  delete [] v;
}

void test_dataset(const std::string& dataset_filename, const std::string& query_filename)
{

  unsigned int dim = 7;
  Dataset dataset(dim);

  float* v = new float[dim];

  FILE* dataset_file = fopen(dataset_filename.c_str(), "r");
  if(!dataset_file)
  {
    std::cout << "data file does not exist" << std::endl;
    return;
  }

  while(!feof(dataset_file))
  {
    for(unsigned int i = 0; i < dim; ++i)
    {
      float tmp;
      fscanf(dataset_file, "%f", &tmp);
      v[i] = tmp;
    }

    dataset.insert(v, dim);
  }

  fclose(dataset_file);

  std::cout << "dataset loaded: " << dataset.size() << std::endl;

  FILE* query_file = fopen(query_filename.c_str(), "r");
  if(!query_file)
  {
    std::cout << "query file does not exist" << std::endl;
    return;
  }

  Dataset line_origin(dim);
  Dataset line_direction(dim);

  while(!feof(query_file))
  {
    for(unsigned int i = 0; i < dim; ++i)
    {
      float tmp;
      fscanf(query_file, "%f", &tmp);
      v[i] = tmp;
    }

    line_origin.insert(v, dim);

    for(unsigned int i = 0; i < dim; ++i)
    {
      float tmp;
      fscanf(query_file, "%f", &tmp);
      v[i] = tmp;
    }

    line_direction.insert(v, dim);
  }

  fclose(query_file);

  delete [] v;


  typedef MultiProbeLSH MyLSH;
  typedef LinearHashTable<unsigned int, unsigned int, __gnu_cxx::hash<unsigned int> > HashTable;
  typedef MultiProbeLSHIndex<HashTable, unsigned int> Index;

  Dataset::Accessor accessor(dataset);
  Index index;

  unsigned int embed_dim = (dim + 2) * (dim + 3) / 2;
  unsigned int M = 5;
  unsigned int L = 20;
  unsigned int K = 50;
  unsigned int H = 100000;
  float W = 1.0;
  float R = std::numeric_limits<float>::max();
  unsigned int T = 5;
  DefaultRng rng;
  metric::l2sqr<float> l2(embed_dim);
  dotprod<float> l2dot(dim);

  // set embedding
  ReducedEmbedding embed(dim);
  float param_c = 106.204;

  // compute lsh index
  Index::Parameter param;
  param.repeat = M;
  param.range = H;
  param.W = W;
  param.dim = embed_dim;

  index.init(param, rng, L);

  for(unsigned int i = 0; i < dataset.size(); ++i)
  {
    // insert an item to the hash table
    embed.t() = sqrt(param_c - 1 - l2dot(dataset[i], dataset[i]));
    index.insert(i, embed.embedded_datapoint(dataset[i]));
  }

  eval::PointToLineEval eval(dim);
  TopkScanner<Dataset::Accessor, const float*, eval::PointToLineEval> scanner(accessor, &eval, K, R);


  Timer timer;
  timer.start();

  //for(unsigned int i = 0; i < line_origin.size(); ++i)
  for(unsigned int i = 0; i < line_origin.size(); ++i)
  {
    std::cout << i << std::endl;
    accessor.reset();
    eval.reset(line_direction[i], line_origin[i]);
    scanner.reset(embed.embedded_line(line_direction[i], line_origin[i]));
    index.query(T, scanner);

    //for(unsigned int j = 0; j < scanner.topk().size(); ++j)
    //{
    //  std::cout << "(" << scanner.topk()[j].key << " " << scanner.topk()[j].dist << ") ";
    //}
    //std::cout << std::endl;
  }

  timer.stop();
  std::cout << "overall timing: " << timer.getElapsedTimeInSec() << std::endl;
  std::cout << "dataset size: " << dataset.size() << std::endl;

}


void line_knn(const Dataset& dataset, float* a, float* v)
{
  typedef Tail<RepeatHash<GaussianLSH> > MyLSH;
  typedef LinearHashTable<unsigned int, unsigned int, __gnu_cxx::hash<unsigned int> > HashTable;
  typedef LSHIndex<MyLSH, HashTable, unsigned int> Index;

  Dataset::Accessor accessor(dataset);
  Index index;

  unsigned int dim = dataset.dim();
  unsigned int embed_dim = (dim + 2) * (dim + 3) / 2;
  unsigned int M = 5;
  unsigned int L = 20;
  unsigned int K = 50;
  unsigned int H = 1000;
  float W = 2.0;
  float R = std::numeric_limits<float>::max();
  DefaultRng rng;
  metric::l2sqr<float> l2(embed_dim);
  dotprod<float> l2dot(dim);

  // set embedding
  ReducedEmbedding embed(dim);
  float param_c = 0;
  for(unsigned int i = 0; i < dataset.size(); ++i)
  {
    float tmp = l2dot(dataset[i], dataset[i]);
    if(tmp > param_c) param_c = tmp;
  }
  param_c += 1;


  // compute lsh index
  Index::Parameter param;
  param.repeat = M;
  param.range = H;
  param.W = W;
  param.dim = embed_dim;

  index.init(param, rng, L);

  for(unsigned int i = 0; i < dataset.size(); ++i)
  {
    // insert an item to the hash table
    embed.t() = sqrt(param_c - 1 - l2dot(dataset[i], dataset[i]));
    index.insert(i, embed.embedded_datapoint(dataset[i]));
  }

  eval::PointToLineEval eval(dim);
  TopkScanner<Dataset::Accessor, const float*, eval::PointToLineEval> scanner(accessor, &eval, K, R);

  Timer timer;
  timer.start();

  accessor.reset();
  eval.reset(v, a);
  scanner.reset(embed.embedded_line(v, a));
  index.query(scanner);

  timer.stop();
  std::cout << "timing: " << timer.getElapsedTime() << std::endl;

  for(unsigned int j = 0; j < scanner.topk().size(); ++j)
  {
    std::cout << "(" << scanner.topk()[j].key << " " << scanner.topk()[j].dist << ") ";
  }
  std::cout << std::endl;
}


void line_mpknn(const Dataset& dataset, float* a, float* v)
{
  typedef MultiProbeLSH MyLSH;
  typedef LinearHashTable<unsigned int, unsigned int, __gnu_cxx::hash<unsigned int> > HashTable;
  typedef MultiProbeLSHIndex<HashTable, unsigned int> Index;

  Dataset::Accessor accessor(dataset);
  Index index;

  unsigned int dim = dataset.dim();
  unsigned int embed_dim = (dim + 2) * (dim + 3) / 2;
  unsigned int M = 5;
  unsigned int L = 20;
  unsigned int K = 50;
  unsigned int H = 1000;
  float W = 2.0;
  float R = std::numeric_limits<float>::max();
  unsigned int T = 5;
  DefaultRng rng;
  metric::l2sqr<float> l2(embed_dim);
  dotprod<float> l2dot(dim);

  // set embedding
  ReducedEmbedding embed(dim);
  float param_c = 0;
  for(unsigned int i = 0; i < dataset.size(); ++i)
  {
    float tmp = l2dot(dataset[i], dataset[i]);
    if(tmp > param_c) param_c = tmp;
  }
  param_c += 1;


  // compute lsh index
  Index::Parameter param;
  param.repeat = M;
  param.range = H;
  param.W = W;
  param.dim = embed_dim;

  index.init(param, rng, L);

  for(unsigned int i = 0; i < dataset.size(); ++i)
  {
    // insert an item to the hash table
    embed.t() = sqrt(param_c - 1 - l2dot(dataset[i], dataset[i]));
    index.insert(i, embed.embedded_datapoint(dataset[i]));
  }

  eval::PointToLineEval eval(dim);
  TopkScanner<Dataset::Accessor, const float*, eval::PointToLineEval> scanner(accessor, &eval, K, R);

  Timer timer;
  timer.start();

  accessor.reset();
  eval.reset(v, a);
  scanner.reset(embed.embedded_line(v, a));
  index.query(T, scanner);

  timer.stop();
  std::cout << "timing: " << timer.getElapsedTime() << std::endl;


  for(unsigned int j = 0; j < scanner.topk().size(); ++j)
  {
    std::cout << "(" << scanner.topk()[j].key << " " << scanner.topk()[j].dist << ") ";
  }
  std::cout << std::endl;
}

void line_rpknn(const Dataset& dataset, float* a, float* v)
{
  typedef Tail<RepeatHash<GaussianLSH> > MyLSH;
  typedef LinearHashTable<unsigned int, unsigned int, __gnu_cxx::hash<unsigned int> > HashTable;
  typedef LSHIndex<MyLSH, HashTable, unsigned int> Index;

  Dataset::Accessor accessor(dataset);
  Index index;

  unsigned int dim = dataset.dim();
  unsigned int embed_dim = (dim + 2) * (dim + 3) / 2;
  unsigned int reduced_dim = std::min<unsigned int>(embed_dim, 20);
  unsigned int M = 5;
  unsigned int L = 20;
  unsigned int K = 50;
  unsigned int H = 1000;
  float W = 2.0;
  float R = std::numeric_limits<float>::max();
  DefaultRng rng;
  metric::l2sqr<float> l2(embed_dim);
  dotprod<float> l2dot(dim);

  // set embedding
  ReducedEmbedding embed(dim);
  float param_c = 0;
  for(unsigned int i = 0; i < dataset.size(); ++i)
  {
    float tmp = l2dot(dataset[i], dataset[i]);
    if(tmp > param_c) param_c = tmp;
  }
  param_c += 1;

  DistanceConservativeRandomProjector rd(embed_dim, reduced_dim);
  // DistanceConservativeSparseRandomProjector rd(embed_dim, reduced_dim, 3.0);
  rd.init(rng);

  // compute lsh index
  Index::Parameter param;
  param.repeat = M;
  param.range = H;
  param.W = W;
  param.dim = embed_dim;

  index.init(param, rng, L);

  for(unsigned int i = 0; i < dataset.size(); ++i)
  {
    // insert an item to the hash table
    embed.t() = sqrt(param_c - 1 - l2dot(dataset[i], dataset[i]));
    index.insert(i, rd(embed.embedded_datapoint(dataset[i])));
  }

  eval::PointToLineEval eval(dim);
  TopkScanner<Dataset::Accessor, const float*, eval::PointToLineEval> scanner(accessor, &eval, K, R);

  Timer timer;
  timer.start();

  accessor.reset();
  eval.reset(v, a);
  scanner.reset(rd(embed.embedded_line(v, a)));
  index.query(scanner);

  timer.stop();
  std::cout << "timing: " << timer.getElapsedTime() << std::endl;

  for(unsigned int j = 0; j < scanner.topk().size(); ++j)
  {
    std::cout << "(" << scanner.topk()[j].key << " " << scanner.topk()[j].dist << ") ";
  }
  std::cout << std::endl;
}

void line_spase_rpknn(const Dataset& dataset, float* a, float* v)
{
  typedef Tail<RepeatHash<SparseGaussianLSH> > MyLSH;
  typedef LinearHashTable<unsigned int, unsigned int, __gnu_cxx::hash<unsigned int> > HashTable;
  typedef LSHIndex<MyLSH, HashTable, unsigned int> Index;

  Dataset::Accessor accessor(dataset);
  Index index;

  unsigned int dim = dataset.dim();
  unsigned int embed_dim = (dim + 2) * (dim + 3) / 2;
  unsigned int reduced_dim = std::min<unsigned int>(embed_dim, 20);
  unsigned int M = 5;
  unsigned int L = 20;
  unsigned int K = 50;
  unsigned int H = 1000;
  float W = 2.0;
  float R = std::numeric_limits<float>::max();
  DefaultRng rng;
  metric::l2sqr<float> l2(embed_dim);
  dotprod<float> l2dot(dim);
  unsigned int sampleN = 20;

  // set embedding
  ReducedEmbedding embed(dim);
  float param_c = 0;
  for(unsigned int i = 0; i < dataset.size(); ++i)
  {
    float tmp = l2dot(dataset[i], dataset[i]);
    if(tmp > param_c) param_c = tmp;
  }
  param_c += 1;

  DotProductConservativeRandomProjector rd(embed_dim);
  rd.init(sampleN, rng);

  // compute lsh index
  Index::Parameter param;
  param.repeat = M;
  param.range = H;
  param.W = W;
  param.dim = embed_dim;

  index.init(param, rng, L);

  for(unsigned int i = 0; i < dataset.size(); ++i)
  {
    // insert an item to the hash table
    embed.t() = sqrt(param_c - 1 - l2dot(dataset[i], dataset[i]));
    index.insert(i, rd(embed.embedded_datapoint(dataset[i])));
  }

  eval::PointToLineEval eval(dim);
  TopkScanner<Dataset::Accessor, SparseVector, eval::PointToLineEval> scanner(accessor, &eval, K, R);

  Timer timer;
  timer.start();

  accessor.reset();
  eval.reset(v, a);
  scanner.reset(rd(embed.embedded_line(v, a)));
  index.query(scanner);

  timer.stop();
  std::cout << "timing: " << timer.getElapsedTime() << std::endl;

  for(unsigned int j = 0; j < scanner.topk().size(); ++j)
  {
    std::cout << "(" << scanner.topk()[j].key << " " << scanner.topk()[j].dist << ") ";
  }
  std::cout << std::endl;

}


void bruteforce_line_knn2(const Dataset& dataset, float* a, float* v)
{
  unsigned int dim = dataset.dim();
  unsigned int embed_dim = (dim + 2) * (dim + 3) / 2;
  ReducedEmbedding embed(dim);
  dotprod<float> l2dot(dim);

  float param_c = 0;
  for(unsigned int i = 0; i < dataset.size(); ++i)
  {
    float tmp = l2dot(dataset[i], dataset[i]);
    if(tmp > param_c) param_c = tmp;
  }
  param_c += 1;

  Dataset newdataset(embed_dim);
  for(unsigned int i = 0; i < dataset.size(); ++i)
  {
    embed.t() = sqrt(param_c - 1 - l2dot(dataset[i], dataset[i]));
    newdataset.insert(embed.embedded_datapoint(dataset[i]), embed_dim);
  }

  Timer timer;
  timer.start();

  bruteforce_knn(newdataset, embed.embedded_line(v, a));

  timer.stop();
  std::cout << "timing: " << timer.getElapsedTime() << std::endl;
}

void bruteforce_line_knn(const Dataset& dataset, float* a, float* v)
{
  Dataset::Accessor accessor(dataset);

  unsigned int K = 50;
  float R = std::numeric_limits<float>::max();
  eval::PointToLineEval eval(dataset.dim());
  Bruteforce<Dataset::Accessor, eval::PointToLineEval> bruteforce(accessor, &eval, K, R);

  accessor.reset();
  eval.reset(v, a);
  bruteforce.reset();
  bruteforce.knn();

  for(unsigned int j = 0; j < bruteforce.topk().size(); ++j)
  {
    std::cout << "(" << bruteforce.topk()[j].key << " " << bruteforce.topk()[j].dist << ") ";
  }
  std::cout << std::endl;
}


void bruteforce_knn(const Dataset& dataset, float* query)
{
  Dataset::Accessor accessor(dataset);

  unsigned int K = 50;
  float R = std::numeric_limits<float>::max();
  metric::l2sqr<float> l2(dataset.dim());
  eval::PointEval<metric::l2sqr<float> > eval(l2);

  Bruteforce<Dataset::Accessor, eval::PointEval<metric::l2sqr<float> > > bruteforce(accessor, &eval, K, R);


  Timer timer;
  timer.start();

  accessor.reset();
  eval.reset(query);
  bruteforce.reset();
  bruteforce.knn();

  timer.stop();
  std::cout << "timing: " << timer.getElapsedTime() << std::endl;

  for(unsigned int j = 0; j < bruteforce.topk().size(); ++j)
  {
    std::cout << "(" << bruteforce.topk()[j].key << " " << sqrt(bruteforce.topk()[j].dist) << ") ";
  }
  std::cout << std::endl;
}



void generate_data(Dataset& dataset, unsigned int N, unsigned int dim)
{
  float ub = 10, lb = 0;

  float* v = new float[dim];

  for(unsigned int i = 0; i < N; ++i)
  {
    for(unsigned int d = 0; d < dim; ++d)
      v[d] = (ub - lb) * (rand() / (float)RAND_MAX) + lb;
    dataset.insert(v, dim);
  }

  delete [] v; v = NULL;
}

